package cn.dansj.utils.calendar;

import cn.dansj.common.utils.transfer.DateTime;
import cn.dansj.common.utils.transfer.DefaultMethods;

import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 计算农历节假日信息
 * SimpleCalendar.Element element=SimpleCalendar.getCalendarDetail("2019-06-06","yyyy-MM-dd");
 * element=SimpleCalendar.getCalendarDetail("2019-06-04","yyyy-MM-dd");
 */
public class SimpleCalendar {
    long[] lunarInfo = new long[]{
            0x4bd8, 0x4ae0, 0xa570, 0x54d5, 0xd260, 0xd950, 0x5554, 0x56af, 0x9ad0, 0x55d2,
            0x4ae0, 0xa5b6, 0xa4d0, 0xd250, 0xd255, 0xb54f, 0xd6a0, 0xada2, 0x95b0, 0x4977,
            0x497f, 0xa4b0, 0xb4b5, 0x6a50, 0x6d40, 0xab54, 0x2b6f, 0x9570, 0x52f2, 0x4970,
            0x6566, 0xd4a0, 0xea50, 0x6a95, 0x5adf, 0x2b60, 0x86e3, 0x92ef, 0xc8d7, 0xc95f,
            0xd4a0, 0xd8a6, 0xb55f, 0x56a0, 0xa5b4, 0x25df, 0x92d0, 0xd2b2, 0xa950, 0xb557,
            0x6ca0, 0xb550, 0x5355, 0x4daf, 0xa5b0, 0x4573, 0x52bf, 0xa9a8, 0xe950, 0x6aa0,
            0xaea6, 0xab50, 0x4b60, 0xaae4, 0xa570, 0x5260, 0xf263, 0xd950, 0x5b57, 0x56a0,
            0x96d0, 0x4dd5, 0x4ad0, 0xa4d0, 0xd4d4, 0xd250, 0xd558, 0xb540, 0xb6a0, 0x95a6,
            0x95bf, 0x49b0, 0xa974, 0xa4b0, 0xb27a, 0x6a50, 0x6d40, 0xaf46, 0xab60, 0x9570,
            0x4af5, 0x4970, 0x64b0, 0x74a3, 0xea50, 0x6b58, 0x5ac0, 0xab60, 0x96d5, 0x92e0,
            0xc960, 0xd954, 0xd4a0, 0xda50, 0x7552, 0x56a0, 0xabb7, 0x25d0, 0x92d0, 0xcab5,
            0xa950, 0xb4a0, 0xbaa4, 0xad50, 0x55d9, 0x4ba0, 0xa5b0, 0x5176, 0x52bf, 0xa930,
            0x7954, 0x6aa0, 0xad50, 0x5b52, 0x4b60, 0xa6e6, 0xa4e0, 0xd260, 0xea65, 0xd530,
            0x5aa0, 0x76a3, 0x96d0, 0x4afb, 0x4ad0, 0xa4d0, 0xd0b6, 0xd25f, 0xd520, 0xdd45,
            0xb5a0, 0x56d0, 0x55b2, 0x49b0, 0xa577, 0xa4b0, 0xaa50, 0xb255, 0x6d2f, 0xada0,
            0x4b63, 0x937f, 0x49f8, 0x4970, 0x64b0, 0x68a6, 0xea5f, 0x6b20, 0xa6c4, 0xaaef,
            0x92e0, 0xd2e3, 0xc960, 0xd557, 0xd4a0, 0xda50, 0x5d55, 0x56a0, 0xa6d0, 0x55d4,
            0x52d0, 0xa9b8, 0xa950, 0xb4a0, 0xb6a6, 0xad50, 0x55a0, 0xaba4, 0xa5b0, 0x52b0,
            0xb273, 0x6930, 0x7337, 0x6aa0, 0xad50, 0x4b55, 0x4b6f, 0xa570, 0x54e4, 0xd260,
            0xe968, 0xd520, 0xdaa0, 0x6aa6, 0x56df, 0x4ae0, 0xa9d4, 0xa4d0, 0xd150, 0xf252,
            0xd520};
    List<Element> elements = new ArrayList<>();
    private static final Map<String, SimpleCalendar> cache = new HashMap<>();
    long[] solarMonth = new long[]{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    String[] Gan = new String[]{"甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"};
    String[] Zhi = new String[]{"子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"};
    String[] Animals = new String[]{"鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊", "猴", "鸡", "狗", "猪"};
    String[] solarTerm = new String[]{"小寒", "大寒", "立春", "雨水", "惊蛰", "春分", "清明", "谷雨", "立夏", "小满", "芒种", "夏至", "小暑", "大暑", "立秋", "处暑", "白露", "秋分", "寒露", "霜降", "立冬", "小雪", "大雪", "冬至"};
    int[] sTermInfo = new int[]{0, 21208, 42467, 63836, 85337, 107014, 128867, 150921, 173149, 195551, 218072, 240693, 263343, 285989, 308563, 331033, 353350, 375494, 397447, 419210, 440795, 462224, 483532, 504758};
    char[] nStr1 = new char[]{'日', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十'};
    String[] nStr2 = new String[]{"初", "十", "廿", "卅", " "};

    static String[] monthChinese = new String[]{"正月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "冬月", "腊月"};

    //国历节日
    String[] sFtv = new String[]{
            "0101 元旦节",
            "0214 情人节",
            "0308 妇女节",
            "0312 植树节 孙中山逝世纪念日",
            "0315 消费者权益保护日",
            "0401 愚人节",
            "0501 劳动节",
            "0504 青年节",
            "0519 汶川地震哀悼日",
            "0601 儿童节",
            "0701 建党节 香港回归纪念日",
            "0707 抗日战争纪念日",
            "0801 八一建军节",
            "0815 日本正式宣布无条件投降日",
            "0909 毛主席逝世纪念日",
            "0910 教师节",
            "0918 九·一八事变纪念日",
            "0928 孔子诞辰",
            "1001 国庆节",
            "1010 辛亥革命纪念日",
            "1031 万圣节",
            "1112 孙中山诞辰",
            "1212 西安事变纪念日",
            "1213 南京大屠杀",
            "1220 澳门回归纪念日",
            "1224 平安夜",
            "1225 圣诞节",
            "1226 毛主席诞辰"
    };
    //农历节日
    String[] lFtv = new String[]{
            "0101 春节",
            "0115 元宵节",
            "0202 龙抬头",
            "0404 寒食节",
            "0505 端午节",
            "0624 火把节",
            "0707 七夕情人节",
            "0714 鬼节(南方)",
            "0715 中元节",
            "0730 地藏节",
            "0815 中秋节",
            "0909 重阳节",
            "1001 寒衣节",
            "1015 下元节",
            "1208 腊八节",
            "1223 北方小年",
            "1224 南方小年",
            "1230 除夕"};

    public static Element getCalendarDetail(Date date) throws ParseException {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        int year = cal.get(Calendar.YEAR);
        int month = cal.get(Calendar.MONTH);
        String cacheKey = (year + "-" + month);
        SimpleCalendar lunarCalendarUtil = cache.get(cacheKey);
        if (lunarCalendarUtil == null) {
            lunarCalendarUtil = new SimpleCalendar();
            lunarCalendarUtil.calendar(year, month);
            cache.put(cacheKey, lunarCalendarUtil);
        }
        return lunarCalendarUtil.getElements().get(cal.get(Calendar.DATE) - 1);
    }

    public static List<Element> getCalendars(int year, int month) throws ParseException {
        String cacheKey = (year + "-" + month);
        SimpleCalendar lunarCalendarUtil = cache.get(cacheKey);
        if (lunarCalendarUtil == null) {
            lunarCalendarUtil = new SimpleCalendar();
            lunarCalendarUtil.calendar(year, month - 1);
            cache.put(cacheKey, lunarCalendarUtil);
        }
        return lunarCalendarUtil.getElements();
    }

    public static Element getCalendarDetail(String date, String pattern) throws ParseException {
        SimpleDateFormat df2 = new SimpleDateFormat(pattern);
        return getCalendarDetail(df2.parse(date));
    }

    public List<Element> getElements() {
        return elements;
    }

    public void setElements(List<Element> elements) {
        this.elements = elements;
    }

    public void calendar(int y, int m) throws ParseException {
        Date sDObj;
        Lunar lDObj;
        boolean lL = false;
        int lY = 0, lM = 0, lD = 1, lX = 0, tmp1, tmp2;
        String cY, cM, cD; //年柱,月柱,日柱
        Integer[] lDPOS = new Integer[3];
        int n = 0;
        int firstLM = 0;
        String dateString = y + "-" + (m + 1) + "-" + 1;
        sDObj = new SimpleDateFormat("yyyy-MM-dd").parse(dateString);

        //公历当月天数
        long length = solarDays(y, m);    //公历当月天数
        //公历当月1日星期几
        int firstWeek = sDObj.getDay();    //公历当月1日星期几

        ////////年柱 1900年立春后为庚子年(60进制36)
        if (m < 2) cY = cyclical(y - 1900 + 36 - 1);
        else cY = cyclical(y - 1900 + 36);
        int term2 = sTerm(y, 2); //立春日期

        ////////月柱 1900年1月小寒以前为 丙子月(60进制12)
        int firstNode = sTerm(y, m * 2);//返回当月「节」为几日开始
        cM = cyclical((y - 1900) * 12L + m + 12);

        //当月一日与 1900/1/1 相差天数
        //1900/1/1与 1970/1/1 相差25567日, 1900/1/1 日柱为甲戌日(60进制10)
        SimpleDateFormat df2 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        df2.setTimeZone(TimeZone.getTimeZone("UTC"));
        Date date = df2.parse("" + y + "-" + (m + 1) + "-" + 1 + " 00:00:00");

        long dayCyclical = date.getTime() / 86400000 + 25567 + 10;
        //// long dayCyclical =date.getTime() / 86400000 + 25567 + 10;
        SimpleDateFormat df3 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        for (int i = 0; i < length; i++) {
            sDObj = df3.parse("" + y + "-" + (m + 1) + "-" + (i + 1) + " 00:00:00");   //当月一日日期
            if (lD > lX) {
                lDObj = new Lunar(sDObj);     //农历
                lY = lDObj.year;           //农历年
                lM = lDObj.month;          //农历月
                lD = lDObj.day;            //农历日
                lL = lDObj.leap;         //农历是否闰月
                lX = lL ? leapDays(lY) : monthDays(lY, lM); //农历当月最后一天

                if (n == 0) firstLM = lM;

                lDPOS[n++] = i - lD + 1;
            }

            //依节气调整二月分的年柱, 以立春为界
            if (m == 1 && (i + 1) == term2) {
                cY = cyclical(y - 1900 + 36);
            }
            //依节气月柱, 以「节」为界
            if ((i + 1) == firstNode) {
                cM = cyclical((y - 1900) * 12L + m + 13);
            }
            //日柱
            cD = cyclical(dayCyclical + i);
            Element element = new Element(y, m + 1, i + 1, lY, lM, lD++, lL, cY, cM, cD, sDObj.getDay(), DateTime.getInstance(sDObj).weekInMonth());//y, m + 1, i + 1
            element.setlDayChinese(cDay(element.getlDay()));
            elements.add(element);
        }

        //节气
        tmp1 = sTerm(y, m * 2) - 1;
        tmp2 = sTerm(y, m * 2 + 1) - 1;
        elements.get(tmp1).solarTerms = solarTerm[m * 2];
        elements.get(tmp2).solarTerms = solarTerm[m * 2 + 1];

        Pattern p = Pattern.compile("^(\\d{2})(\\d{2})([\\s\\*])(.+)$");
        //国历节日
        for (String i : sFtv) {
            Matcher matcher = p.matcher(i);
            if (matcher.matches() && Integer.parseInt(matcher.group(1)) == (m + 1)) {
                elements.get(Integer.parseInt(matcher.group(2)) - 1).solarFestival += matcher.group(4) + "";
            }
        }

        p = Pattern.compile("^(\\d{2})(.{2})([\\s\\*])(.+)$");
        //农历节日
        for (String i : lFtv) {
            Matcher matcher = p.matcher(i);
            if (matcher.matches()) {
                tmp1 = Integer.parseInt(matcher.group(1)) - firstLM;
                if (tmp1 == -11) tmp1 = 1;
                if (tmp1 >= 0 && tmp1 < n) {
                    tmp2 = lDPOS[tmp1] + Integer.parseInt(matcher.group(2)) - 1;
                    if (tmp2 >= 0 && tmp2 < length) {
                        elements.get(tmp2).lunarFestival += matcher.group(4);
                    }
                }
            }
        }

        //复活节只出现在3或4月
        if (m == 2 || m == 3) {
            Easter estDay = new Easter(y);
            if (m == estDay.m)
                elements.get(estDay.d - 1).solarFestival = elements.get(estDay.d - 1).solarFestival + " 复活节";
        }
    }

    //==============================返回公历 y年某m+1月的天数
    public long solarDays(int y, int m) {
        if (m == 1)
            return (((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0)) ? 29 : 28);
        else
            return (solarMonth[m]);
    }

    //============================== 传入 offsenew Datet 返回干支, 0=甲子
    public String cyclical(long num) {
        return (Gan[(int) (num % 10)] + Zhi[(int) (num % 12)]);
    }

    //======================  中文日期
    public String cDay(int d) {
        String s;

        switch (d) {
            case 10:
                s = "初十";
                break;
            case 20:
                s = "二十";
                break;
            case 30:
                s = "三十";
                break;
            default:
                s = nStr2[Double.valueOf(Math.floor(d / 10)).intValue()];
                s += nStr1[d % 10];
        }
        return (s);
    }

    //===== 某年的第n个节气为几日(从0小寒起算)
    public int sTerm(int y, int n) throws ParseException {
        SimpleDateFormat df2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        df2.setTimeZone(TimeZone.getTimeZone("UTC"));
        Date date = df2.parse("1900-01-06 02:05:00");
        long utcTime2 = date.getTime();
        BigDecimal time2 = new BigDecimal("31556925974.7").multiply(new BigDecimal(y - 1900)).add(new BigDecimal(sTermInfo[n]).multiply(BigDecimal.valueOf(60000L)));
        BigDecimal time = time2.add(BigDecimal.valueOf(utcTime2));
        Date offDate = new Date(time.longValue());
        Calendar cal = Calendar.getInstance();
        cal.setTimeZone(TimeZone.getTimeZone("UTC"));
        cal.setTime(offDate);
        //日期从0算起
        return cal.get(Calendar.DATE);
    }

    //====================================== 返回农历 y年闰哪个月 1-12 , 没闰返回 0
    public Long leapMonth(int y) {
        long lm = lunarInfo[y - 1900] & 0xf;
        return (lm == 0xf ? 0 : lm);
    }

    //====================================== 返回农历 y年的总天数
    public Long lYearDays(int y) {
        long i, sum = 348;
        for (i = 0x8000; i > 0x8; i >>= 1) sum += (lunarInfo[y - 1900] & i) != 0 ? 1 : 0;
        return (sum + leapDays(y));
    }

    //====================================== 返回农历 y年闰月的天数
    public int leapDays(int y) {
        if (leapMonth(y) != 0) return ((lunarInfo[y - 1899] & 0xf) == 0xf ? 30 : 29);
        else return 0;
    }

    //====================================== 返回农历 y年m月的总天数
    private int monthDays(int y, int m) {
        return ((lunarInfo[y - 1900] & (0x10000 >> m)) != 0 ? 30 : 29);
    }

    public class Lunar {
        private final int year;
        private boolean leap;
        private final int month;
        private final int day;

        public Lunar(Date objDate) throws ParseException {
            int i, leap, temp = 0;
            SimpleDateFormat df2 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
            df2.setTimeZone(TimeZone.getTimeZone("UTC"));
            DateFormat dtFmt = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
            Date date = df2.parse(dtFmt.format(objDate));
            SimpleDateFormat df3 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
            df3.setTimeZone(TimeZone.getTimeZone("UTC"));
            Date date3 = df3.parse("" + 1900 + "-" + 1 + "-" + 31 + " 00:00:00");
            long time1 = date.getTime();
            long time2 = date3.getTime();
            int offset = (int) ((time1 - time2) / 86400000);
            for (i = 1900; i < 2100 && offset > 0; i++) {
                temp = lYearDays(i).intValue();
                offset -= temp;
            }

            if (offset < 0) {
                offset += temp;
                i--;
            }

            this.year = i;
            leap = leapMonth(i).intValue(); //闰哪个月
            this.leap = false;

            for (i = 1; i < 13 && offset > 0; i++) {
                //闰月
                if (leap > 0 && i == (leap + 1) && !this.leap) {
                    --i;
                    this.leap = true;
                    temp = leapDays(this.year);
                } else {
                    temp = monthDays(this.year, i);
                }

                //解除闰月
                if (this.leap && i == (leap + 1)) this.leap = false;

                offset -= temp;
            }

            if (offset == 0 && leap > 0 && i == leap + 1)
                if (this.leap) {
                    this.leap = false;
                } else {
                    this.leap = true;
                    --i;
                }

            if (offset < 0) {
                offset += temp;
                --i;
            }

            this.month = i;
            this.day = offset + 1;
        }


    }

    public class Easter {

        public int m;
        public int d;

        public Easter(int y) throws ParseException {
            int term2 = sTerm(y, 5); //取得春分日期
            SimpleDateFormat df2 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
            df2.setTimeZone(TimeZone.getTimeZone("UTC"));
            Date dayTerm2 = df2.parse("" + y + "-" + 3 + "-" + term2 + " 00:00:00");//取得春分的公历日期控件(春分一定出现在3月)
            Lunar lDayTerm2 = new Lunar(dayTerm2); //取得取得春分农历
            int lMlen = 0;
            if (lDayTerm2.day < 15) //取得下个月圆的相差天数
                lMlen = 15 - lDayTerm2.day;
            else
                lMlen = (lDayTerm2.leap ? leapDays(y) : monthDays(y, lDayTerm2.month)) - lDayTerm2.day + 15;

            //一天等于 1000*60*60*24 = 86400000 毫秒
            Date l15 = new Date(dayTerm2.getTime() + 86400000L * lMlen); //求出第一次月圆为公历几日
            Date dayEaster = new Date(l15.getTime() + 86400000 * (7 - l15.getDay())); //求出下个周日

            this.m = dayEaster.getMonth();
            this.d = dayEaster.getDate();
        }
    }

    public static class Element extends DefaultMethods {
        private int sYear;
        private int sMonth;
        private int sDay;
        private int week;
        private int lYear;
        private int lMonth;
        private String lMonthChinese;
        private String lDayChinese;
        private int lDay;
        private boolean leap;
        private String cYear;
        private String cMonth;
        private String cDay;
        private String lunarFestival;
        private String solarFestival;
        private String solarTerms;
        private int weekInMonth;
        private String sDate;

        public Element(int sYear, int sMonth, int sDay, int lYear, int lMonth, int lDay, boolean leap, String cYear, String cMonth, String cDay, int week, int weekInMonth) {
            this.sDate = sYear + "-" + (sMonth > 10 ? sMonth : "0" + sMonth) + "-" + (sDay > 10 ? sDay : "0" + sDay);
            //瓣句
            this.sYear = sYear;   //公元年4位数字
            this.sMonth = sMonth;  //公元月数字
            this.sDay = sDay;    //公元日数字
            //农历
            this.lYear = lYear;   //公元年4位数字
            this.lMonth = lMonth;  //农历月数字
            this.lDay = lDay;    //农历日数字
            this.leap = leap;  //是否为农历闰月?
            //中文
            this.lMonthChinese = monthChinese[lMonth - 1];
            //八字
            this.cYear = cYear;   //年柱, 2个中文
            this.cMonth = cMonth;  //月柱, 2个中文
            this.cDay = cDay;    //日柱, 2个中文

            this.lunarFestival = ""; //农历节日
            this.solarFestival = ""; //公历节日
            this.solarTerms = ""; //节气
            this.weekInMonth = weekInMonth;
            this.week = week == 0 ? 7 : week;

            if (sMonth == 5 && weekInMonth == 2 && this.week == 7) {
                this.solarFestival += "母亲节";
            }
            if (sMonth == 6 && weekInMonth == 3 && this.week == 7) {
                this.solarFestival += "父亲节";
            }
        }

        public int getsYear() {
            return sYear;
        }

        public void setsYear(int sYear) {
            this.sYear = sYear;
        }

        public int getsMonth() {
            return sMonth;
        }

        public void setsMonth(int sMonth) {
            this.sMonth = sMonth;
        }

        public int getsDay() {
            return sDay;
        }

        public void setsDay(int sDay) {
            this.sDay = sDay;
        }

        public int getlYear() {
            return lYear;
        }

        public void setlYear(int lYear) {
            this.lYear = lYear;
        }

        public int getlMonth() {
            return lMonth;
        }

        public void setlMonth(int lMonth) {
            this.lMonth = lMonth;
        }

        public int getlDay() {
            return lDay;
        }

        public void setlDay(int lDay) {
            this.lDay = lDay;
        }

        public int getWeek() {
            return week;
        }

        public void setWeek(int week) {
            this.week = week;
        }

        public boolean isLeap() {
            return leap;
        }

        public void setLeap(boolean leap) {
            this.leap = leap;
        }

        public String getcYear() {
            return cYear;
        }

        public void setcYear(String cYear) {
            this.cYear = cYear;
        }

        public String getcMonth() {
            return cMonth;
        }

        public void setcMonth(String cMonth) {
            this.cMonth = cMonth;
        }

        public String getcDay() {
            return cDay;
        }

        public void setcDay(String cDay) {
            this.cDay = cDay;
        }

        public int getWeekInMonth() {
            return weekInMonth;
        }

        public void setWeekInMonth(int weekInMonth) {
            this.weekInMonth = weekInMonth;
        }

        public String getLunarFestival() {
            return lunarFestival;
        }

        public void setLunarFestival(String lunarFestival) {
            this.lunarFestival = lunarFestival;
        }

        public String getSolarFestival() {
            return solarFestival;
        }

        public void setSolarFestival(String solarFestival) {
            this.solarFestival = solarFestival;
        }

        public String getSolarTerms() {
            return solarTerms;
        }

        public void setSolarTerms(String solarTerms) {
            this.solarTerms = solarTerms;
        }

        public String getlMonthChinese() {
            return lMonthChinese;
        }

        public void setlMonthChinese(String lMonthChinese) {
            this.lMonthChinese = lMonthChinese;
        }

        public String getlDayChinese() {
            return lDayChinese;
        }

        public void setlDayChinese(String lDayChinese) {
            this.lDayChinese = lDayChinese;
        }

        public String getsDate() {
            return sDate;
        }

        public void setsDate(String sDate) {
            this.sDate = sDate;
        }
    }
}